package net.protocol.diameter.message;

import net.protocol.diameter.avp.AVP;
import net.protocol.diameter.avp.AVPFactory;
import net.protocol.diameter.dictionary.MessageFormat;
import net.protocol.diameter.dictionary.MessageFormatFactory;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * This class provides a skeletal implementation of the {@link DiameterMessage}
 * interface to minimize the effort required to implement this interface.
 * 
 * @author jinhongw@gmail.com
 */
public class AbstractDiameterMessage implements DiameterMessage {
	public final static byte DEFAULT_VERSION = 1;
    
    private final DiameterHeader header;
    private Collection<AVP<?>> avps = new ArrayList<AVP<?>>();

    /**
     * @param commandCode Command Code
     */
    protected AbstractDiameterMessage(int commandCode) {
        this(DEFAULT_VERSION, commandCode);
    }

    /**
     * @param version     Version
     * @param commandCode Command Code
     */
    protected AbstractDiameterMessage(byte version, int commandCode) {
        this.header = new DiameterHeader(version, commandCode);
    }
    
	/**
	 * Creates a new <code>DiameterMessage</code> with the given
	 * <code>MessageFormatFactory</code> and <code>ByteBuffer</code>
	 * 
	 * @param factory
	 *            A specified implementation of the <tt>MessageFormatFactory</tt> interface
	 * @param src
	 *            The buffer from which bytes are to be retrieved
	 */
    public AbstractDiameterMessage(MessageFormatFactory factory, ByteBuffer src) {
        this.header = new DiameterHeader(src);

        int appId = header.getApplicationID();
        int code = header.getCommandCode();
        MessageFormat mf = factory.get(appId, code, header.isRequest());
        addAll(AVPFactory.createMsgAVPs(mf, src));
    }
   
    @Override
    public ByteBuffer byteMe(ByteBuffer dst) {
        header.byteMe(dst);
        
        return dst;
    }

    @Override
    public boolean isRequest() {
        return header.isRequest();
    }

    @Override
    public boolean isProxiable() {
        return header.isProxiable();
    }

    @Override
    public boolean isError() {
        return header.isError();
    }

    @Override
    public boolean isReTransmitted() {
        return header.isReTransmitted();
    }

    @Override
    public void setProxiable(boolean value) {
        header.setProxiable(value);
    }

    @Override
    public void setError(boolean value) {
        header.setError(value);
    }

    @Override
    public void setReTransmitted(boolean value) {
        header.setError(value);
    }

    @Override
    public byte getVersion() {
        return header.getVersion();
    }

    @Override
    public int getMessageLength() {
        return header.getMessageLength();
    }

    @Override
    public void setMessageLength(int messageLength) {
        header.setMessageLength(messageLength);
    }

    @Override
    public byte getCommandFlags() {
        return header.getCommandFlags();
    }

    @Override
    public int getCommandCode() {
        return header.getCommandCode();
    }

    @Override
    public int getApplicationID() {
        return header.getApplicationID();
    }

    @Override
    public void setApplicationID(int applicationID) {
        header.setApplicationID(applicationID);
    }

    @Override
    public long getHopByHopIdentifier() {
        return header.getHopByHopIdentifier();
    }

    @Override
    public void setHopByHopIdentifier(long hopByHopIdentifier) {
        header.setHopByHopIdentifier(hopByHopIdentifier);
    }

    @Override
    public long getEndToEndIdentifier() {
        return header.getEndToEndIdentifier();
    }

    @Override
    public void setEndToEndIdentifier(long endToEndIdentifier) {
        header.setEndToEndIdentifier(endToEndIdentifier);
    }
    
    /**
     * Returns an iterator over the AVP<?> in this DiameterMessage.
     *
     * @return an <tt>Iterator</tt> over the AVP<?> in this DiameterMessage
     */
    @Override
    public Iterator<AVP<?>> iterator() {
        return avps.iterator();
    }
    
    @Override
    public boolean add(AVP<?> AVP) {
    	return avps.add(AVP);
    }

    @Override
    public boolean addAll(Collection<AVP<?>> avps) {
        return this.avps.addAll(avps);
    }

    @Override
    public int size() {
        return (avps == null) ? 0 : avps.size();
    }
    
    @Override
    public boolean isEmpty() {
        return (avps == null) || (avps.size() == 0);
    }
    
    /**
     * If set, the message is a request. If cleared, the message is an answer.
     *
     * @param value whether to set message is a request
     */
    protected void setRequest(boolean value) {
        header.setRequest(value);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;

        AbstractDiameterMessage other = (AbstractDiameterMessage) obj;
        if (header == null) {
            if (other.header != null)
                return false;
        } else if (!header.equals(other.header))
            return false;

        if (avps == null) {
            if (other.avps != null)
                return false;
        } else if (!avps.equals(other.avps))
            return false;

        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((header == null) ? 0 : header.hashCode());
        result = prime * result + ((avps == null) ? 0 : avps.hashCode());
        return result;
    }
    
    @Override
	public String toString() {
		StringBuilder buf = new StringBuilder("Diameter Message:");
		buf.append("\n");
		buf.append("Version: ").append(getVersion()).append("\n");
		buf.append("Msg Length: ").append(getMessageLength()).append("\n");
		buf.append("Cmd Flags: ").append(getCommandFlags()).append("\n");
		buf.append("Cmd Code: ").append(getCommandCode()).append("\n");
		buf.append("App-Id: ").append(getApplicationID()).append("\n");
		buf.append("Hop-By-Hop-Id: ").append(getHopByHopIdentifier()).append("\n");
		buf.append("End-To-End-Id: ").append(getEndToEndIdentifier());
		
		for (AVP<?> e : avps) {
			buf.append("\n  ").append(e);
		}
		return buf.toString();
	}
}
